// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Global

/* XFontSource.cs --
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Diagnostics;
using System.Globalization;

using PdfSharpCore.Fonts;
using PdfSharpCore.Fonts.OpenType;

#endregion

#nullable enable

namespace PdfSharpCore.Drawing;

/// <summary>
/// The bytes of a font file.
/// </summary>
[DebuggerDisplay ("{DebuggerDisplay}")]
internal class XFontSource
{
    // Implementation Notes
    //
    // * XFontSource represents a single font (file) in memory.
    // * An XFontSource hold a reference to it OpenTypeFontface.
    // * To prevent large heap fragmentation this class must exists only once.
    // * TODO: ttcf

    // Signature of a true type collection font.
    const uint ttcf = 0x66637474;

    XFontSource (byte[] bytes, ulong key)
    {
        FontName = null;
        Bytes = bytes;
        _key = key;
    }

    /// <summary>
    /// Gets an existing font source or creates a new one.
    /// A new font source is cached in font factory.
    /// </summary>
    public static XFontSource GetOrCreateFrom (byte[] bytes)
    {
        var key = FontHelper.CalcChecksum (bytes);
        XFontSource fontSource;
        if (!FontFactory.TryGetFontSourceByKey (key, out fontSource))
        {
            fontSource = new XFontSource (bytes, key);

            // Theoretically the font source could be created by a differend thread in the meantime.
            fontSource = FontFactory.CacheFontSource (fontSource);
        }

        return fontSource;
    }

    public static XFontSource GetOrCreateFrom (string typefaceKey, byte[] fontBytes)
    {
        XFontSource fontSource;
        var key = FontHelper.CalcChecksum (fontBytes);
        if (FontFactory.TryGetFontSourceByKey (key, out fontSource))
        {
            // The font source already exists, but is not yet cached under the specified typeface key.
            FontFactory.CacheExistingFontSourceWithNewTypefaceKey (typefaceKey, fontSource);
        }
        else
        {
            // No font source exists. Create new one and cache it.
            fontSource = new XFontSource (fontBytes, key);
            FontFactory.CacheNewFontSource (typefaceKey, fontSource);
        }

        return fontSource;
    }

    public static XFontSource CreateCompiledFont (byte[] bytes)
    {
        var fontSource = new XFontSource (bytes, 0);
        return fontSource;
    }

    /// <summary>
    /// Gets or sets the fontface.
    /// </summary>
    internal OpenTypeFontface? Fontface
    {
        get => _fontface;
        set
        {
            _fontface = value;
            FontName = value?._name?.FullFontName;
        }
    }

    private OpenTypeFontface? _fontface;

    /// <summary>
    /// Gets the key that uniquely identifies this font source.
    /// </summary>
    internal ulong Key
    {
        get
        {
            if (_key == 0)
            {
                _key = FontHelper.CalcChecksum (Bytes);
            }

            return _key;
        }
    }

    ulong _key;

    public void IncrementKey()
    {
        // HACK: Depends on implementation of CalcChecksum.
        // Increment check sum and keep length untouched.
        _key += 1ul << 32;
    }

    /// <summary>
    /// Gets the name of the font's name table.
    /// </summary>
    public string? FontName { get; private set; }

    /// <summary>
    /// Gets the bytes of the font.
    /// </summary>
    public byte[] Bytes { get; }

    /// <inheritdoc cref="object.GetHashCode"/>
    public override int GetHashCode()
    {
        return (int)((Key >> 32) ^ Key);
    }

    /// <inheritdoc cref="object.Equals(object?)"/>
    public override bool Equals (object? obj)
    {
        if (obj is not XFontSource fontSource)
        {
            return false;
        }

        return Key == fontSource.Key;
    }

    /// <summary>
    /// Gets the DebuggerDisplayAttribute text.
    /// </summary>

    // ReSha rper disable UnusedMember.Local
    internal string DebuggerDisplay =>

        // The key is converted to a value a human can remember during debugging.
        string.Format (CultureInfo.InvariantCulture, "XFontSource: '{0}', keyhash={1}", FontName,
            Key % 99991 /* largest prime number less than 100000 */); // ReShar per restore UnusedMember.Local
}
